#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <assert.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "common.h"

using namespace std;

#define COM_SIZE 1024
#define ARGV_SIZE 64
#define DEF_CHAR " "

void split(char *argv[ARGV_SIZE], char *ps)
{
    assert(argv && ps);

    // 调用 C语言 中的 strtok 函数分割字符串
    int pos = 0;
    argv[pos++] = strtok(ps, DEF_CHAR); // 有空格就分割
    while (argv[pos++] = strtok(NULL, DEF_CHAR))
        ; // 不断分割

    argv[pos] = NULL; // 确保安全
}

void showEnv()
{
    extern char **environ; // 使用当前进行的环境变量表
    int pos = 0;
    for (; environ[pos]; printf("%s\n", environ[pos++]))
        ;
}

// 枚举类型，用于判断不同的文件打开方式
enum redir
{
    REDIR_INPUT = 0,
    REDIR_OUTPUT,
    REDIR_APPEND,
    REDIR_NONE
} redir_type = REDIR_NONE; // 创建对象 redir_type，默认为 NONE

// 检查是否出现重定向符
char *checkDir(char *command)
{
    // 从右往左遍历，遇到 > >> < 就置为 '\0'
    size_t end = strlen(command); // 与返回值相匹配
    char *ps = command + end;     // 为了避免出现无符号-1，这里采取错位的方法
    while (end != 0)
    {
        if (command[end - 1] == '>')
        {
            if (command[end - 2] == '>')
            {
                command[end - 2] = '\0';
                redir_type = REDIR_APPEND;
                return ps;
            }

            command[end - 1] = '\0';
            redir_type = REDIR_OUTPUT;
            return ps;
        }
        else if (command[end - 1] == '<')
        {
            command[end - 1] = '\0';
            redir_type = REDIR_INPUT;
            return ps;
        }

        // 如果不是空格，就可以更新 ps指向
        if (*(command + end - 1) != ' ')
            ps = command + end - 1;

        end--;
    }

    return NULL; // 如果没有重定向符，就返回空
}

int main()
{
    char myEnv[COM_SIZE][ARGV_SIZE]; // 大小与前面有关
    int env_pos = 0;                 // 专门维护缓冲区
    int exit_code = 0;               // 保存退出码的全局变量

    // 2023.6.7 更新
    // 创建管道文件
    int ret = mkfifo(fifo_name.c_str(), mode);
    assert(ret != -1);
    (void)ret;

    // 打开管道文件
    int rfd = open(fifo_name.c_str(), O_RDONLY);
    assert(rfd != -1);
    (void)rfd;

    // 这是一个始终运行的程序：bash
    while (1)
    {
        char command[COM_SIZE]; // 存放指令的数组（缓冲区）

        // 打印提示符
        printf("[User@myBash default]$ ");
        fflush(stdout);

        // 读取指令
        //从管道中读取
        int n = read(rfd, command, COM_SIZE - 1);

        if(n == 0)
        {
            cout << "写端已关闭，读端也即将关闭" << endl;
            break;
        }

        command[n] = '\0';
        cout << command << endl;

        // 重定向
        // 在获取指令后进行判断
        // 如果成立，则获取目标文件名 filename
        char *filename = checkDir(command);

        // 指令分割
        // 将连续的指令分割为 argv 表
        char *argv[ARGV_SIZE];
        split(argv, command);

        // 特殊处理
        // 颜色高亮处理，识别是否为 ls 指令
        if (strcmp(argv[0], "ls") == 0)
        {
            int pos = 0;
            while (argv[pos++])
                ;                                   // 找到尾
            argv[pos - 1] = (char *)"--color=auto"; // 添加此字段
            argv[pos] = NULL;                       // 结尾
        }

        // 目录间移动处理
        if (strcmp(argv[0], "cd") == 0)
        {
            // 直接调用接口，然后 continue 不再执行后续代码
            if (strcmp(argv[1], "~") == 0)
                chdir("/home"); // 回到家目录
            else if (strcmp(argv[1], "-") == 0)
                chdir(getenv("OLDPWD"));
            else if (argv[1])
                chdir(argv[1]); // argv[1] 中就是路径
            continue;           // 终止此次循环
        }

        // 环境变量相关
        if (strcmp(argv[0], "export") == 0)
        {
            if (argv[1])
            {
                strcpy(myEnv[env_pos], argv[1]);
                putenv(myEnv[env_pos++]);
            }
            continue; // 一样需要提前结束循环
        }

        // 环境变量表
        if (strcmp(argv[0], "env") == 0)
        {
            showEnv(); // 调用函数，打印父进程的环境变量表
            continue;  // 提前结束本次循环
        }

        // echo 相关
        // 只有 echo $ 才做特殊处理（环境变量+退出码）
        if (strcmp(argv[0], "echo") == 0 && argv[1][0] == '$')
        {
            if (argv[1] && argv[1][0] == '$')
            {
                if (argv[1][1] == '?')
                    printf("%d\n", exit_code);
                else
                    printf("%s\n", getenv(argv[1] + 1));
            }

            continue;
        }

        // 子进程进行程序替换
        pid_t id = fork();
        if (id == 0)
        {
            // 判断是否需要进行重定向
            if (redir_type == REDIR_INPUT)
            {
                int fd = open(filename, O_RDONLY);
                dup2(fd, 0); // 更改输入，读取文件 filename
            }
            else if (redir_type == REDIR_OUTPUT)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                dup2(fd, 1); // 写入
            }
            else if (redir_type == REDIR_APPEND)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
                dup2(fd, 1); // 追加
            }

            // 直接执行程序替换，这里使用 execvp
            execvp(argv[0], argv);

            exit(168); // 替换失败后返回
        }

        // 父进程等待子进程终止
        int status = 0;
        waitpid(id, &status, 0); // 在等待队列中阻塞
        exit_code = WEXITSTATUS(status);
        if (WIFEXITED(status))
        {
            // 假如程序替换失败
            if (exit_code == 168)
                printf("%s: Error - %s\n", argv[0], "The directive is not yet defined");
        }
        else
            printf("process run fail! [code_dump]:%d [exit_signal]:%d\n", (status >> 7) & 1, status & 0x7F); // 子进程异常终止的情况
    }

    //关闭管道文件
    close(rfd);
    unlink(fifo_name.c_str());
    return 0;
}
